// Copyright (c) 2013-2015 by Felix Rusu, LowPowerLab.com
// SPI Flash memory library for arduino/moteino.
// This works with 256byte/page SPI flash memory
// For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes)
// Minimal modifications should allow chips that have different page size but modifications
// DEPENDS ON: Arduino SPI library
// > Updated Jan. 5, 2015, TomWS1, modified writeBytes to allow blocks > 256 bytes and handle page misalignment.
// > Updated Feb. 26, 2015 TomWS1, added support for SPI Transactions (Arduino 1.5.8 and above)
// > Selective merge by Felix after testing in IDE 1.0.6, 1.6.4
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
#ifdef USE_SPIFLASH
#include <SPIFlash.h>
// #include <core_debug.h>
#include <cstring>
#include <wiring_constants.h>
#include <digital_io.h>
#include <pins_arduino.h>
//#include <logger_rte.h>

uint8_t SPIFlash::UNIQUEID[8];
extern "C" void core_debug(const char *format, ...);
/// IMPORTANT: NAND FLASH memory requires erase before write, because
///            it can only transition from 1s to 0s and only the erase command can reset all 0s to 1s
/// See http://en.wikipedia.org/wiki/Flash_memory
/// The smallest range that can be erased is a sector (4K, 32K, 64K); there is also a chip erase command

/// Constructor. JedecID is optional but recommended, since this will ensure that the device is present and has a valid response
/// get this from the datasheet of your flash chip
/// Example for Atmel-Adesto 4Mbit AT25DF041A: 0x1F44 (page 27: http://www.adestotech.com/sites/default/files/datasheets/doc3668.pdf)
/// Example for Winbond 4Mbit W25X40CL: 0xEF30 (page 14: http://www.winbond.com/NR/rdonlyres/6E25084C-0BFE-4B25-903D-AE10221A0929/0/W25X40CL.pdf)

SPIFlash::SPIFlash(nSPI *_spi, PinName slaveSelectPin, uint16_t jedecID) : _jedecID(jedecID) {
    spi = _spi;
    _slaveSelectPin = slaveSelectPin;
}

/// Select the flash chip
void SPIFlash::sel() {
    // save current SPI settings
#ifndef SPI_HAS_TRANSACTION
    noInterrupts();
#endif
#if defined(SPCR) && defined(SPSR)
    _SPCR = SPCR;
    _SPSR = SPSR;
#endif

#ifdef SPI_HAS_TRANSACTION
    spi->beginTransaction(_settings);
#else
    // set FLASH SPI settings
    spi->setDataMode(SPI_MODE3);
    spi->setBitOrder(MSBFIRST);
    spi->setClockDivider(SPI_CLOCK_DIV4); // decided to slow down from DIV2 after SPI stalling in some instances, especially visible on mega1284p when RFM69 and FLASH chip both present
#endif
    digitalWriteFast(_slaveSelectPin, LOW);
}

/// UNselect the flash chip
void SPIFlash::unselect() {
    digitalWriteFast(_slaveSelectPin, HIGH);
    // restore SPI settings to what they were before talking to the FLASH chip
#ifdef SPI_HAS_TRANSACTION
    spi->endTransaction();
#else
    interrupts();
#endif
#if defined(SPCR) && defined(SPSR)
    SPCR = _SPCR;
    SPSR = _SPSR;
#endif
}

void SPIFlash::disableGlobalBlockProtect() {
    //    if (_chip.memoryTypeID == SST25) {
    command(05);
    spi->transfer(0xff);
    command(SPIFLASH_STATUSWRITE, true);
    spi->transfer(0);
    unselect();
    //    }
    //    else if (_chip.memoryTypeID == SST26) {
    //      if(!_notBusy()) {
    //      	return false;
    //      }
    //      _writeEnable();
    //      _delay_us(10);
    //      _beginSPI(ULBPR);
    //      CHIP_DESELECT
    //      _delay_us(50);
    //      _writeDisable();
    //    }
    //    return true;
}

/// setup SPI, read device ID etc...
int SPIFlash::init() {
#if defined(SPCR) && defined(SPSR)
    _SPCR = SPCR;
    _SPSR = SPSR;
#endif
#ifdef SPI_HAS_TRANSACTION
    _settings = SPISettings(30000000, MSBFIRST, SPI_MODE0);
#endif
    pin_function(_slaveSelectPin, STM_PIN_DATA(STM_MODE_OUTPUT_PP, GPIO_NOPULL, 0));
    spi->begin();
    sel();
    unselect();
    wakeup();
    disableGlobalBlockProtect();
    command(SPIFLASH_STATUSREAD);
    int sta1 = spi->transfer(0xff);
    // command(0x50, true);
    unselect();
    command(0x35);
    int sta2 = spi->transfer(0xff);
    unselect();
    command(0x15);
    int sta3 = spi->transfer(0xff);
    unselect();
    command(0x3d);
    int sta4 = spi->transfer(0xff);
    unselect();
    core_debug("\ns1=0x%x,s2=0x%x,s3=0x%x,lock=0x%x\n", sta1, sta2, sta3, sta4);
    // command(0x50, true);
    // command(0x01, true);
    // command(0x01, true);
    // unselect();
    // command(0x66, true);
    // unselect();
    // command(0x99, true);

    unselect();
    if (sta1 != 0) {
        core_debug("Flash Chip Locked!");
        // return false;
    }
    if (_jedecID == 0 || readDeviceId() == _jedecID) {
        command(SPIFLASH_STATUSWRITE, true); // Write Status Register
        spi->transfer(0);
        unselect();
        readDeviceId();
        readUniqueId();
#ifdef SPIFLASH_DEBUG
        // self_test(4095);
#endif
        return true;
    }
    return false;
}

/// Get the manufacturer and device ID bytes (as a short word)
uint16_t SPIFlash::readDeviceId() {
#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo
    command(SPIFLASH_IDREAD);   // Read JEDEC ID
#else
    sel();
#endif
    spi->transfer(SPIFLASH_IDREAD);
    _jedecID = spi->transfer(0) << 16;
    _jedecID |= spi->transfer(0) << 8;
    _jedecID |= spi->transfer(0);
    unselect();
    core_debug("\nCHIP ID: 0x%x\n", _jedecID);
    return (uint16_t) _jedecID;
}

/// Get the 64 bit unique identifier, stores it in UNIQUEID[8]. Only needs to be called once, ie after initialize
/// Returns the byte pointer to the UNIQUEID byte array
/// Read UNIQUEID like this:
/// flash.readUniqueId(); for (uint8_t i=0;i<8;i++) { Serial.print(flash.UNIQUEID[i], HEX); Serial.print(' '); }
/// or like this:
/// flash.readUniqueId(); uint8_t* MAC = flash.readUniqueId(); for (uint8_t i=0;i<8;i++) { Serial.print(MAC[i], HEX); Serial.print(' '); }
uint8_t *SPIFlash::readUniqueId() {
    command(SPIFLASH_MACREAD);
    spi->transfer(0);
    spi->transfer(0);
    spi->transfer(0);
    spi->transfer(0);
    for (unsigned char &i: UNIQUEID)
        i = spi->transfer(0);
    unselect();
    core_debug("\nCHIP UNIQUEID: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", UNIQUEID[0], UNIQUEID[1], UNIQUEID[2],
               UNIQUEID[3], UNIQUEID[4], UNIQUEID[5], UNIQUEID[6], UNIQUEID[7]);
    return UNIQUEID;
}

/// read 1 byte from flash memory
uint8_t SPIFlash::readByte(uint32_t addr) {
    command(SPIFLASH_ARRAYREADLOWFREQ);
    spi->transfer((uint8_t) (addr >> 16));
    spi->transfer((uint8_t) (addr >> 8));
    spi->transfer((uint8_t) addr);
    uint8_t result = spi->transfer(0);

    unselect();
    return result;
}

/// read unlimited # of bytes
int SPIFlash::readBytes(uint32_t addr, void *buf, uint16_t len) {
    memset(buf, 0, len);
    command(SPIFLASH_ARRAYREAD);
    uint8_t b[4];
    b[0] = (uint8_t) (addr >> 16);
    b[1] = (uint8_t) (addr >> 8);
    b[2] = (uint8_t) addr;
    b[3] = 0;
    spi->transfer(b, 4);
    void *txbuf;
    txbuf = (uint8_t *) malloc(len);
    memset(txbuf, 0xff, len);
    spi->transfer((uint8_t *) txbuf, (uint8_t *) buf, len);
    free(txbuf);
    unselect();
    return len;
}

/// Send a command to the flash chip, pass TRUE for isWrite when its a write command
void SPIFlash::command(uint8_t cmd, bool isWrite) {
#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo
    DDRB |= B00000001;          // Make sure the SS pin (PB0 - used by RFM12B on MoteinoLeo R1) is set as output HIGH!
    PORTB |= B00000001;
#endif
    if (isWrite) {
        command(SPIFLASH_WRITEENABLE); // Write Enable
        unselect();
    }
    // wait for any write/erase to complete
    //   a time limit cannot really be added here without it being a very large safe limit
    //   that is because some chips can take several seconds to carry out a chip erase or other similar multi block or entire-chip operations
    //   a recommended alternative to such situations where chip can be or not be present is to add a 10k or similar weak pulldown on the
    //   open drain MISO input which can read noise/static and hence return a non 0 status byte, causing the while() to hang when a flash chip is not present
    uint32_t tick = Rtos::ticks();
    if (cmd != SPIFLASH_WAKE)
        while (busy()) {
            if (Rtos::ticks() - tick > 5000) {
                core_debug("SPI Flash Timeout,command=0x%x\n", cmd);
            }
        }
    sel();
    spi->transfer(cmd);
}

/// check if the chip is busy erasing/writing
bool SPIFlash::busy() {
    /*
    sel();
    spi->transfer(SPIFLASH_STATUSREAD);
    uint8_t status = spi->transfer(0);
    unselect();
    return status & 1;
    */
    return readStatus() & 1;
}

/// return the STATUS register
uint8_t SPIFlash::readStatus() {
    sel();
    spi->transfer(SPIFLASH_STATUSREAD);
    uint8_t status = spi->transfer(0);
    unselect();
    return status;
}

/// Write 1 byte to flash memory
/// WARNING: you can only write to previously erased memory locations (see datasheet)
///          use the block erase commands to first clear memory (write 0xFFs)
void SPIFlash::writeByte(uint32_t addr, uint8_t byt) {
    command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program
    spi->transfer((uint8_t) (addr >> 16));
    spi->transfer((uint8_t) (addr >> 8));
    spi->transfer((uint8_t) addr);
    spi->transfer(byt);

    unselect();
}

/// write multiple bytes to flash memory (up to 64K)
/// WARNING: you can only write to previously erased memory locations (see datasheet)
///          use the block erase commands to first clear memory (write 0xFFs)
/// This version handles both page alignment and data blocks larger than 256 bytes.
///
int SPIFlash::writeBytes(uint32_t iaddr, const void *buf, uint16_t ilen) {
    uint16_t n;
    uint16_t offset = 0;
    uint32_t addr = iaddr;
    uint16_t len = ilen;
    uint16_t maxBytes = 256 - (addr % 256); // force the first set of bytes to stay within the first page
#ifdef SPIFLASH_DEBUG
    check(addr, len, 0xff);
#endif
    uint8_t *rxbuf;
    rxbuf = (uint8_t *) malloc(256);
    while (len > 0) {
        n = (len <= maxBytes) ? len : maxBytes;
        command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program
        uint8_t b[3];
        b[0] = (uint8_t) (addr >> 16);
        b[1] = (uint8_t) (addr >> 8);
        b[2] = (uint8_t) addr;
        spi->transfer(b, 3);
        spi->transfer((uint8_t *) buf + offset, rxbuf, n);
        unselect();

        addr += n; // adjust the addresses and remaining bytes by what we've just transferred.
        offset += n;
        len -= n;
        maxBytes = 256; // now we can do up to 256 bytes per loop
    }
    free(rxbuf);
#ifdef SPIFLASH_DEBUG
    uint8_t *buf_read;
    buf_read = (uint8_t *) malloc(ilen);
    memset(buf_read, 0, ilen);
    readBytes(iaddr, buf_read, ilen);
    for (int i = 0; i < ilen; i++) {
        if (((uint8_t *) buf)[i] != buf_read[i])
            core_debug("write error 0x%x : 0x%x 0x%x ", addr + i, ((uint8_t *) buf)[i], buf_read[i]);
    }
    free(buf_read);
#endif
    return ilen;
}

/// erase entire flash memory array
/// may take several seconds depending on size, but is non blocking
/// so you may wait for this to complete using busy() or continue doing
/// other things and later check if the chip is done with busy()
/// note that any command will first wait for chip to become available using busy()
/// so no need to do that twice
void SPIFlash::chipErase() {
    command(SPIFLASH_CHIPERASE, true);
    unselect();
    Rtos::Delay_us(10);
    while (busy());
}

/// erase a 4Kbyte block
int SPIFlash::blockErase4K(uint32_t addr) {
    command(SPIFLASH_BLOCKERASE_4K, true); // Block Erase
    spi->transfer((uint8_t) (addr >> 16));
    spi->transfer((uint8_t) (addr >> 8));
    spi->transfer((uint8_t) addr);
    unselect();
#ifdef SPIFLASH_DEBUG
    for (int i = 0; i < 4096; i += 256)
        check(addr + i, 256, 0xff);
#endif
    return 0;
}

int SPIFlash::checkEmpty(uint32_t addr, uint16_t blockCount) {
    for (int i = 0; i < blockCount; i++) {
        uint8_t buf[4096];
        readBytes(addr, buf, 4096);
        for (unsigned char j: buf) {
            if (j != 0xff)
                return -1;
        }
    }
    return 0;
}

/// erase a 32Kbyte block
void SPIFlash::blockErase32K(uint32_t addr) {
    command(SPIFLASH_BLOCKERASE_32K, true); // Block Erase
    spi->transfer((uint8_t) (addr >> 16));
    spi->transfer((uint8_t) (addr >> 8));
    spi->transfer((uint8_t) addr);
    unselect();
}

/// erase a 64Kbyte block
void SPIFlash::blockErase64K(uint32_t addr) {
    command(SPIFLASH_BLOCKERASE_64K, true); // Block Erase
    spi->transfer((uint8_t) (addr >> 16));
    spi->transfer((uint8_t) (addr >> 8));
    spi->transfer((uint8_t) addr);
    unselect();
}

void SPIFlash::sleep() {
    command(SPIFLASH_SLEEP);
    unselect();
}

void SPIFlash::wakeup() {
    command(SPIFLASH_WAKE);
    unselect();
}

/// cleanup
void SPIFlash::end() {
    spi->end();
}

void SPIFlash::self_test(uint32_t block) {
    uint8_t buf[512];
    blockErase4K(block * 4096);
    for (int i = 0; i < 512; i++)
        buf[i] = (uint8_t) i;
    writeBytes(block * 4096, buf, 512);
    readBytes(block * 4096, buf, 512);
    core_debug("\n");
    for (int i = 0; i < 32; i++) {
        for (int j = 0; j < 16; j++) {
            core_debug(" %x ", buf[i * 16 + j]);
        }
        core_debug("\n");
    }
}

void SPIFlash::check(uint32_t addr, uint16_t len, uint8_t num) {
    uint8_t *buf;
    buf = (uint8_t *) malloc(len);
    memset(buf, 0, len);
    readBytes(addr, buf, len);
    for (int i = 0; i < len; i++) {
        if (buf[i] != num)
            core_debug("error 0x%x : 0x%x 0x%x ", addr + i, buf[i], num);
    }
    free(buf);
}

SPIFlash::SPIFlash(nSPI *_spi, int slaveSelectPin, uint16_t jedecID) : _jedecID(jedecID) {
    spi = _spi;
    _slaveSelectPin = digitalPinToPinName(slaveSelectPin);
}

#endif
